/**
 * Copyright (C) 2021-2022 maminjie <canpool@163.com>
 * SPDX-License-Identifier: LGPL-2.1
 * Powered by Qt-Advanced-Docking-System
**/

#include "docktitlebar.h"
#include "docktitlebar_p.h"

#include "qads_global.h"
#include "docktabbar.h"
#include "dockpannel.h"
#include "dockmanager.h"
#include "dockmanager_p.h"
#include "dockoverlay.h"
#include "dockwidget.h"
#include "docktab.h"
#include "dockfloatingwidget.h"
#include "dockfloatingdragpreview.h"
#include "dockiconprovider.h"
#include "dockfocuscontroller.h"

#include <QBoxLayout>
#include <QLoggingCategory>
#include <QMenu>
#include <QMouseEvent>
#include <QPushButton>
#include <QScrollArea>
#include <QStyle>

#include <iostream>

QADS_BEGIN_NAMESPACE

class DockTitleBarPrivate
{
public:
    DockTitleBarPrivate(DockTitleBar *parent);

    void createTabBar();
    void createButtons();

    DockManager *dockManager() const {
        return m_dockPannel->dockManager();
    }

    bool isDraggingState(DragState dragState) const {
        return this->m_dragState == dragState;
    }

    void startFloating(const QPoint &offset);
    DockFloatingInterface *makeFloating(const QPoint &offset, DragState dragState);

public:
    DockTitleBar *q;
    QBoxLayout *m_layout = nullptr;
    DockPannel *m_dockPannel = nullptr;
    DockTabBar *m_tabBar = nullptr;
    QPointer<DockTitleBarButton> m_tabsMenuButton;
    QPointer<DockTitleBarButton> m_undockButton;
    QPointer<DockTitleBarButton> m_closeButton;
    QList<DockTitleBarButton *> m_dockWidgetActionsButtons;
    DockFloatingInterface *m_floatingWidget = nullptr;

    bool m_menuOutdated = true;
    QPoint m_dragStartPos;
    DragState m_dragState = DraggingInactive;
}; // class DockTitleBarPrivate

DockTitleBarPrivate::DockTitleBarPrivate(DockTitleBar *parent)
    : q(parent)
{}

void DockTitleBarPrivate::createTabBar()
{
    m_tabBar = new DockTabBar(m_dockPannel);
    m_tabBar->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
    m_layout->addWidget(m_tabBar);
    QObject::connect(m_tabBar, &DockTabBar::tabClosed, q, &DockTitleBar::markTabsMenuOutdated);
    QObject::connect(m_tabBar, &DockTabBar::tabOpened, q, &DockTitleBar::markTabsMenuOutdated);
    QObject::connect(m_tabBar, &DockTabBar::tabInserted, q, &DockTitleBar::markTabsMenuOutdated);
    QObject::connect(m_tabBar, &DockTabBar::tabRemoving, q, &DockTitleBar::markTabsMenuOutdated);
    QObject::connect(m_tabBar, &DockTabBar::tabMoved, q, &DockTitleBar::markTabsMenuOutdated);
    QObject::connect(m_tabBar, &DockTabBar::currentChanged, q, &DockTitleBar::onCurrentTabChanged);
    QObject::connect(m_tabBar, &DockTabBar::tabBarClicked, q, &DockTitleBar::tabBarClicked);
    QObject::connect(m_tabBar, &DockTabBar::tabElidedChanged, q, &DockTitleBar::markTabsMenuOutdated);
}

void DockTitleBarPrivate::createButtons()
{
    const QSize iconSize(14, 14);
    QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);

    // Tabs menu button
    m_tabsMenuButton = new DockTitleBarButton(QAds::testConfigFlag(QAds::TitleBarHasTabsMenuButton));
    m_tabsMenuButton->setObjectName("tabsMenuButton");
    m_tabsMenuButton->setAutoRaise(true);
    m_tabsMenuButton->setPopupMode(QToolButton::InstantPopup);
    internal::setButtonIcon(m_tabsMenuButton, QStyle::SP_TitleBarUnshadeButton, QAds::TitleBarTabsMenuIcon);
    QMenu *tabsMenu = new QMenu(m_tabsMenuButton);
#ifndef QT_NO_TOOLTIP
    tabsMenu->setToolTipsVisible(true);
#endif
    QObject::connect(tabsMenu, &QMenu::aboutToShow, q, &DockTitleBar::onTabsMenuAboutToShow);
    m_tabsMenuButton->setMenu(tabsMenu);
    internal::setToolTip(m_tabsMenuButton, QObject::tr("List All Tabs"));
    m_tabsMenuButton->setSizePolicy(sizePolicy);
    m_tabsMenuButton->setIconSize(iconSize);
    m_layout->addWidget(m_tabsMenuButton, 0);
    QObject::connect(m_tabsMenuButton->menu(), &QMenu::triggered, q, &DockTitleBar::onTabsMenuActionTriggered);

    // Undock button
    m_undockButton = new DockTitleBarButton(QAds::testConfigFlag(QAds::TitleBarHasUndockButton));
    m_undockButton->setObjectName("detachPannelButton");
    m_undockButton->setAutoRaise(true);
    internal::setToolTip(m_undockButton, QObject::tr("Detach Pannel"));
    internal::setButtonIcon(m_undockButton, QStyle::SP_TitleBarNormalButton, QAds::TitleBarUndockIcon);
    m_undockButton->setSizePolicy(sizePolicy);
    m_undockButton->setIconSize(iconSize);
    m_layout->addWidget(m_undockButton, 0);
    QObject::connect(m_undockButton, &QToolButton::clicked, q, &DockTitleBar::onUndockButtonClicked);

    // Close button
    m_closeButton = new DockTitleBarButton(QAds::testConfigFlag(QAds::TitleBarHasCloseButton));
    m_closeButton->setObjectName("dockPannelCloseButton");
    m_closeButton->setAutoRaise(true);
    internal::setButtonIcon(m_closeButton, QStyle::SP_TitleBarCloseButton, QAds::TitleBarCloseIcon);
    if (QAds::testConfigFlag(QAds::TitleBarCloseButtonClosesTab)) {
        internal::setToolTip(m_closeButton, QObject::tr("Close Active Tab"));
    } else {
        internal::setToolTip(m_closeButton, QObject::tr("Close Pannel"));
    }

    m_closeButton->setSizePolicy(sizePolicy);
    m_closeButton->setIconSize(iconSize);
    m_layout->addWidget(m_closeButton, 0);
    QObject::connect(m_closeButton, &QToolButton::clicked, q, &DockTitleBar::onCloseButtonClicked);
}

DockFloatingInterface *DockTitleBarPrivate::makeFloating(const QPoint &offset, DragState dragState)
{
    m_dragState = dragState;
    QSize size = m_dockPannel->size();
    bool opaqueUndocking = QAds::testConfigFlag(QAds::OpaqueUndocking) || (DraggingFloatingWidget != dragState);
    DockFloatingWidget *floatingWidget = nullptr;
    DockFloatingInterface *ifloating;

    if (opaqueUndocking) {
        ifloating = floatingWidget = new DockFloatingWidget(m_dockPannel);
    } else {
        auto w = new DockFloatingDragPreview(m_dockPannel);
        QObject::connect(w, &DockFloatingDragPreview::draggingCanceled, [ = ]() {
            m_dragState = DraggingInactive;
        });
        ifloating = w;
    }

    ifloating->startFloating(offset, size, dragState, nullptr);

    if (floatingWidget) {
        auto topLevelDockWidget = floatingWidget->topLevelDockWidget();
        if (topLevelDockWidget) {
            topLevelDockWidget->emitTopLevelChanged(true);
        }
    }

    return ifloating;
}

void DockTitleBarPrivate::startFloating(const QPoint &offset)
{
    m_floatingWidget = makeFloating(offset, DraggingFloatingWidget);
}

/* DockTitleBarButton */
DockTitleBarButton::DockTitleBarButton(bool visible, QWidget *parent)
    : QToolButton(parent)
    , m_visible(visible)
    , m_hideWhenDisabled(QAds::testConfigFlag(QAds::TitleBarHideDisabledButtons))
{
    setAttribute(Qt::WA_StyledBackground, true);
    setObjectName("qads_titlebarbutton");
}

void DockTitleBarButton::setVisible(bool visible)
{
    // 'visible' can stay 'true' if and only if this button is configured to generaly visible:
    visible = visible && m_visible;

    // 'visible' can stay 'true' unless: this button is configured to be invisible when it
    // is disabled and it is currently disabled:
    if (visible && m_hideWhenDisabled) {
        visible = isEnabled();
    }

    QToolButton::setVisible(visible);
}

bool DockTitleBarButton::event(QEvent *event)
{
    if (QEvent::EnabledChange == event->type() && m_hideWhenDisabled) {
        // force setVisible() call
        // Calling setVisible() directly here doesn't work well when button is expected to be shown first time
        const bool visible = isEnabled();
        QMetaObject::invokeMethod(this, [this, visible] { setVisible(visible); }, Qt::QueuedConnection);
    }

    return QToolButton::event(event);
}

DockTitleBarSpacer::DockTitleBarSpacer(QWidget *parent)
    : QWidget(parent)
{
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    setStyleSheet("border: none; background: transparent;");
}

/* DockTitleBar */
DockTitleBar::DockTitleBar(DockPannel *parent)
    : QWidget(parent)
    , d(new DockTitleBarPrivate(this))
{
    Q_ASSERT(parent);
    setAttribute(Qt::WA_StyledBackground, true);
    setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);

    d->m_dockPannel = parent;
    setObjectName("dockTitleBar");

    d->m_layout = new QBoxLayout(QBoxLayout::LeftToRight);
    d->m_layout->setContentsMargins(0, 0, 0, 0);
    d->m_layout->setSpacing(0);
    setLayout(d->m_layout);

    d->createTabBar();
    d->m_layout->addWidget(new DockTitleBarSpacer(this));
    d->createButtons();

    setFocusPolicy(Qt::NoFocus);
}

DockTitleBar::~DockTitleBar()
{
    if (!d->m_closeButton.isNull()) {
        delete d->m_closeButton;
    }

    if (!d->m_tabsMenuButton.isNull()) {
        delete d->m_tabsMenuButton;
    }

    if (!d->m_undockButton.isNull()) {
        delete d->m_undockButton;
    }

    delete d;
}

DockTabBar *DockTitleBar::tabBar() const
{
    return d->m_tabBar;
}

QAbstractButton *DockTitleBar::button(TitleBarButtonRole which) const
{
    switch (which) {
    case TabsMenuButton:
        return d->m_tabsMenuButton;
    case UndockButton:
        return d->m_undockButton;
    case CloseButton:
        return d->m_closeButton;
    default:
        return nullptr;
    }
}

void DockTitleBar::updateDockWidgetActionsButtons()
{
    DockTab *tab = d->m_tabBar->currentTab();
    if (tab == nullptr) {
        return;
    }

    if (!d->m_dockWidgetActionsButtons.isEmpty()) {
        for (auto button : qAsConst(d->m_dockWidgetActionsButtons)) {
            d->m_layout->removeWidget(button);
            delete button;
        }

        d->m_dockWidgetActionsButtons.clear();
    }

    DockWidget *dockWidget = tab->dockWidget();
    auto actions = dockWidget->titleBarActions();
    if (actions.isEmpty()) {
        return;
    }

    int insertIndex = d->m_layout->indexOf(d->m_tabsMenuButton);
    for (auto action : qAsConst(actions)) {
        auto button = new DockTitleBarButton(true, this);
        button->setDefaultAction(action);
        button->setAutoRaise(true);
        button->setPopupMode(QToolButton::InstantPopup);
        button->setObjectName(action->objectName());
        d->m_layout->insertWidget(insertIndex++, button, 0);
        d->m_dockWidgetActionsButtons.append(button);
    }
}

void DockTitleBar::setVisible(bool visible)
{
    QWidget::setVisible(visible);
    markTabsMenuOutdated();
}

/**
 * Inserts a custom widget at position index into this title bar.
 * If index is negative, the widget is added at the end.
 * You can use this function to insert custom widgets into the title bar.
 */
void DockTitleBar::insertWidget(int index, QWidget *widget)
{
    d->m_layout->insertWidget(index, widget);
}

/**
 * Searches for widget widget in this title bar.
 * You can use this function, to get the position of the default
 * widget in the tile bar.
 * \code
 * int tabBarIndex = TitleBar->indexOf(TitleBar->tabBar());
 * int closeButtonIndex = TitleBar->indexOf(TitleBar->button(TitleBarButtonClose));
 * \endcode
 */
int DockTitleBar::indexOf(QWidget *widget) const
{
    return d->m_layout->indexOf(widget);
}

void DockTitleBar::markTabsMenuOutdated()
{
    if (QAds::testConfigFlag(QAds::TitleBarDynamicTabsMenuButtonVisibility)) {
        bool hasElidedTabTitle = false;
        for (int i = 0; i < d->m_tabBar->count(); ++i) {
            if (!d->m_tabBar->isTabVisible(i)) {
                continue;
            }
            DockTab *tab = d->m_tabBar->tab(i);
            if (tab->isTitleElided()) {
                hasElidedTabTitle = true;
                break;
            }
        }
        const bool visible = (hasElidedTabTitle && (d->m_tabBar->count() > 1));
        QMetaObject::invokeMethod(this, [this, visible] {
            d->m_tabsMenuButton->setVisible(visible); }, Qt::QueuedConnection);
    }

    d->m_menuOutdated = true;
}

void DockTitleBar::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        event->accept();
        d->m_dragStartPos = event->pos();
        d->m_dragState = DraggingMousePressed;

        if (QAds::testConfigFlag(QAds::FocusHighlighting)) {
            DockTab *tab = d->m_tabBar->currentTab();
            if (tab) {
                //tab->setFocus(Qt::OtherFocusReason);
                d->dockManager()->dockFocusController()->setDockTabFocused(tab);
            }
        }

        return;
    }

    QWidget::mousePressEvent(event);
}

void DockTitleBar::mouseReleaseEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        event->accept();
        auto CurrentDragState = d->m_dragState;
        d->m_dragStartPos = QPoint();
        d->m_dragState = DraggingInactive;

        if (CurrentDragState == DraggingFloatingWidget) {
            d->m_floatingWidget->finishDragging();
        }

        return;
    }

    QWidget::mouseReleaseEvent(event);
}

void DockTitleBar::mouseMoveEvent(QMouseEvent *event)
{
    QWidget::mouseMoveEvent(event);

    if (!(event->buttons() & Qt::LeftButton) || d->isDraggingState(DraggingInactive)) {
        d->m_dragState = DraggingInactive;
        return;
    }

    // move floating window
    if (d->isDraggingState(DraggingFloatingWidget)) {
        d->m_floatingWidget->moveFloating();
        return;
    }

    // If this is the last dock pannel in a floating dock container it does not make
    // sense to move it to a new floating widget and leave this one empty
    if (d->m_dockPannel->dockContainer()->isFloating() &&
        d->m_dockPannel->dockContainer()->visibleDockPannelCount() == 1) {
        return;
    }

    // If one single dock widget in this pannel is not floatable then the whole pannel is not floatable
    // If we do non opaque undocking, then we can create the floating drag preview if the dock widget is movable
    auto features = d->m_dockPannel->features();
    if (!features.testFlag(DockWidget::DockWidgetFloatable) &&
        !(features.testFlag(DockWidget::DockWidgetMovable) && !QAds::testConfigFlag(QAds::OpaqueUndocking))) {
        return;
    }

    int dragDistance = (d->m_dragStartPos - event->pos()).manhattanLength();
    if (dragDistance >= DockManager::startDragDistance()) {
        d->startFloating(d->m_dragStartPos);
        auto overlay = d->m_dockPannel->dockManager()->containerOverlay();
        overlay->setAllowedAreas(OuterDockAreas);
    }

    return;
}

void DockTitleBar::mouseDoubleClickEvent(QMouseEvent *event)
{
    // If this is the last dock area in a dock container it does not make
    // sense to move it to a new floating widget and leave this one empty
    if (d->m_dockPannel->dockContainer()->isFloating()
            && d->m_dockPannel->dockContainer()->dockPannelCount() == 1) {
        return;
    }

    if (!d->m_dockPannel->features().testFlag(DockWidget::DockWidgetFloatable)) {
        return;
    }

    d->makeFloating(event->pos(), DraggingInactive);
}

void DockTitleBar::contextMenuEvent(QContextMenuEvent *event)
{
    event->accept();

    if (d->isDraggingState(DraggingFloatingWidget)) {
        return;
    }

    QMenu menu(this);
    auto action = menu.addAction(tr("Detach Pannel"), this, &DockTitleBar::onUndockButtonClicked);
    action->setEnabled(d->m_dockPannel->features().testFlag(DockWidget::DockWidgetFloatable));
    menu.addSeparator();
    QString closeText = QAds::testConfigFlag(QAds::TitleBarCloseButtonClosesTab) ? tr("Close Active Tab")
                                                                                 : tr("Close Pannel");
    action = menu.addAction(closeText, this, &DockTitleBar::onCloseButtonClicked);
    action->setEnabled(d->m_dockPannel->features().testFlag(DockWidget::DockWidgetClosable));
    menu.addAction(tr("Close Other Pannels"), d->m_dockPannel, &DockPannel::closeOtherPannels);
    menu.exec(event->globalPos());
}

void DockTitleBar::onTabsMenuAboutToShow()
{
    if (!d->m_menuOutdated) {
        return;
    }

    QMenu *menu = d->m_tabsMenuButton->menu();
    menu->clear();

    for (int i = 0; i < d->m_tabBar->count(); ++i) {
        if (!d->m_tabBar->isTabVisible(i)) {
            continue;
        }
        auto tab = d->m_tabBar->tab(i);
        QAction *action = menu->addAction(tab->icon(), tab->text());
        internal::setToolTip(action, tab->toolTip());
        action->setData(i);
    }

    d->m_menuOutdated = false;
}

void DockTitleBar::onCloseButtonClicked()
{
    if (QAds::testConfigFlag(QAds::TitleBarCloseButtonClosesTab)) {
        d->m_tabBar->closeTab(d->m_tabBar->currentIndex());
    } else {
        d->m_dockPannel->closePannel();
    }
}

void DockTitleBar::onUndockButtonClicked()
{
    if (d->m_dockPannel->features().testFlag(DockWidget::DockWidgetFloatable)) {
        d->makeFloating(mapFromGlobal(QCursor::pos()), DraggingInactive);
    }
}

void DockTitleBar::onTabsMenuActionTriggered(QAction *action)
{
    int index = action->data().toInt();
    d->m_tabBar->setCurrentIndex(index);
    emit tabBarClicked(index);
}

void DockTitleBar::onCurrentTabChanged(int index)
{
    if (index < 0) {
        return;
    }

    if (QAds::testConfigFlag(QAds::TitleBarCloseButtonClosesTab)) {
        DockWidget *dockWidget = d->m_tabBar->tab(index)->dockWidget();
        d->m_closeButton->setEnabled(dockWidget->features().testFlag(DockWidget::DockWidgetClosable));
    }

    updateDockWidgetActionsButtons();
}

QADS_END_NAMESPACE
